using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Week1.world
{

    /// <summary>
    /// This is a game component that implements IUpdateable.
    /// </summary>
    public class DrawableBase : DrawableGameComponent, helper.IDebugable
    {
        #region globalvars
        public world.behavior.DrawableGlobalVar oVars;
        #endregion

        public DrawableBase(Game game, string modelName, player.Camera camera, Effect effect, int instanceCount)
            : base(game)
        {
            oVars = new behavior.DrawableGlobalVar(instanceCount, modelName, effect, camera);
        }

        public override void Initialize()
        {
            oVars.oDepthState = new DepthStencilState();
            oVars.oDepthState.DepthBufferEnable = true;

            oVars.oRasterState = new RasterizerState();
            oVars.oRasterState.CullMode = CullMode.CullCounterClockwiseFace;

            if (oVars.iInstanceCount == 0)
            {
                oVars.iInstanceCount = 1;
                oVars.oPosition[0] = new Vector3(0, 0, 0);
                oVars.oWorld[0] = Matrix.CreateWorld(oVars.oPosition[0], Vector3.Forward, Vector3.Up);
            }

            // initialize behavior classes
            for (int iDx = 0; iDx < oVars.iBehaviourCount; iDx++)
            {
                oVars.oBehavior[iDx].initialize(oVars.iBehaviorHandler[iDx], oVars);
            }

            //initDebugRender();

            base.Initialize();
        }

        private void initDebugRender()
        {
            // init all verts
            for (int idx = 0; idx < oVars.iInstanceCount * 4; idx++)
            {
                oVars.oDebugRender[idx] = new VertexPositionColor();
            }
        }

        /// <summary>
        /// register the behavior
        /// </summary>
        /// <param name="iDx"></param>
        /// <param name="behavior"></param>
        /// <param name="trigger"></param>
        /// <returns></returns>
        public int registerBehavior(int iDx, world.behavior.BehaviorBase behavior, world.behavior.TriggerKey trigger)
        {
            if (iDx < oVars.iBehaviourCount)
            {
                oVars.oBehavior[oVars.iBehaviourCount] = behavior;
                oVars.iBehaviorHandler[oVars.iBehaviourCount] = behavior.getBehaviorHandlerID(oVars);
                behavior.setTrigger(oVars.iBehaviorHandler[oVars.iBehaviourCount], trigger);

                if (iDx < 0)
                    oVars.iBehaviourCount++;
            }

            return oVars.iBehaviorHandler[oVars.iBehaviourCount - 1];
        }

        /// <summary>
        /// set position of an instance
        /// </summary>
        /// <param name="index"></param>
        /// <param name="position"></param>
        /// <param name="scale"></param>
        public void setPositionAt(int index, Vector3 position, float scale)
        {
            oVars.oPosition[index].X = position.X;
            oVars.oPosition[index].Y = position.Y;
            oVars.oPosition[index].Z = position.Z;

            if (scale == 0)
                scale = 1;

            oVars.oWorld[index] = Matrix.CreateScale(scale) * Matrix.CreateWorld(oVars.oPosition[index], Vector3.Forward, Vector3.Up);
        }

        /// <summary>
        /// 
        /// </summary>
        protected override void LoadContent()
        {
            oVars.oConsole = helper.DebugConsole.Instance;

            oVars.oModel = this.Game.Content.Load<Model>(oVars.sModelName);

            oVars.oConsole = helper.DebugConsole.Instance;
            registerDebug();

            base.LoadContent();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="gameTime"></param>
        public override void Draw(GameTime gameTime)
        {
            this.GraphicsDevice.DepthStencilState = oVars.oDepthState;
            this.GraphicsDevice.RasterizerState = oVars.oRasterState;
            drawBatchCustomEffect(gameTime);
            //drawBatch(gameTime);
            //drawNoBatch();

            base.Draw(gameTime);
        }

        private void drawDebugBlocks(GameTime gameTime)
        {
            
        }

        /// <summary>
        /// no batching of state
        /// </summary>
        unsafe private void drawNoBatch()
        {
            unsafe
            {
                foreach (ModelMesh oMesh in oVars.oModel.Meshes)
                {
                    for (int iDx = 0; iDx < oVars.iInstanceCount; iDx++)
                    {
                        foreach (BasicEffect oEffect in oMesh.Effects)
                        {

                            oEffect.EnableDefaultLighting();
                            oEffect.PreferPerPixelLighting = true;
                            oEffect.Projection = oVars.oCamera.Projection;
                            oEffect.View = oVars.oCamera.View;
                            oEffect.World = oVars.oWorld[iDx];
                        }
                        oMesh.Draw();
                    }
                }
            }
        }

        /// <summary>
        /// Draw using state batch
        /// </summary>
        /// <param name="gametime"></param>
        public void drawBatch(GameTime gametime)
        {
            foreach (ModelMesh mesh in oVars.oModel.Meshes)
            {
                foreach (ModelMeshPart meshPart in mesh.MeshParts)
                {
                    GraphicsDevice.SetVertexBuffer(meshPart.VertexBuffer, meshPart.VertexOffset);
                    GraphicsDevice.Indices = meshPart.IndexBuffer;

                    // Set up the rendering effect.
                    BasicEffect oEffect = meshPart.Effect as BasicEffect;

                    oEffect.EnableDefaultLighting();
                    oEffect.PreferPerPixelLighting = true;
                    oEffect.Projection = oVars.oCamera.Projection;
                    oEffect.View = oVars.oCamera.View;
                    /*
                    effect.CurrentTechnique = effect.Techniques["NoInstancing"];
                    effect.Parameters["View"].SetValue(view);
                    effect.Parameters["Projection"].SetValue(projection);
                    EffectParameter transformParameter = effect.Parameters["World"];
                    */
                    // Draw a single instance copy each time around this loop.
                    for (int iDx = 0; iDx < oVars.iInstanceCount; iDx++)
                    {
                        oEffect.World = oVars.oWorld[iDx];

                        //transformParameter.SetValue(modelBones[mesh.ParentBone.Index] * instances[i]);

                        foreach (EffectPass pass in oEffect.CurrentTechnique.Passes)
                        {
                            pass.Apply();

                            GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0,
                                                                 meshPart.NumVertices, meshPart.StartIndex,
                                                                 meshPart.PrimitiveCount);
                        }
                    }
                }
            }

        }

        /// <summary>
        /// Draw using state batch
        /// </summary>
        /// <param name="gametime"></param>
        public void drawBatchCustomEffect(GameTime gametime)
        {
            int modelMeshCnt = oVars.oModel.Meshes.Count;
            int meshPartsCnt;
            ModelMesh mesh;
            ModelMeshPart meshPart;
            EffectPass pass;
            int iMeshPartIDx;
            int iInstIDx;
            int iMeshIDx;

            for (iMeshIDx = 0; iMeshIDx < modelMeshCnt; iMeshIDx++)
            {
                mesh = oVars.oModel.Meshes[iMeshIDx];
                meshPartsCnt = mesh.MeshParts.Count;

                for (iMeshPartIDx = 0; iMeshPartIDx < meshPartsCnt; iMeshPartIDx++)
                {
                    meshPart = mesh.MeshParts[iMeshPartIDx];

                    GraphicsDevice.SetVertexBuffer(meshPart.VertexBuffer, meshPart.VertexOffset);
                    GraphicsDevice.Indices = meshPart.IndexBuffer;

                    // Set up the rendering effect.
                    oVars.oEffect = meshPart.Effect;

                    oVars.oEffect.Parameters["View"].SetValue(oVars.oCamera.View);
                    oVars.oEffect.Parameters["Projection"].SetValue(oVars.oCamera.Projection);

                    // there is only one pass
                    pass = oVars.oEffect.CurrentTechnique.Passes[0];

                    // Draw a single instance copy each time around this loop.
                    for (iInstIDx = 0; iInstIDx < oVars.iInstanceCount; iInstIDx++)
                    {
                        oVars.oEffect.Parameters["World"].SetValue(oVars.oWorld[iInstIDx]);

                        pass.Apply();

                        GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0,
                            meshPart.NumVertices, meshPart.StartIndex,
                            meshPart.PrimitiveCount);

                    }
                }
            }

        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="gameTime"></param>
        public override void Update(GameTime gameTime)
        {
            for (int iDx = 0; iDx < oVars.iBehaviourCount; iDx++)
            {
                oVars.oBehavior[iDx].execute(oVars.iBehaviorHandler[iDx], oVars, gameTime);
            }

            base.Update(gameTime);
        }

        public void updateDebugRender()
        {
            for (int idx = 0; idx < oVars.iInstanceCount; idx++)
            {
                oVars.oDebugRender[idx].Position = oVars.oPosition[idx];
            }
        }

        #region DebugRegion
        protected helper.DebugMsg oMsg = new helper.DebugMsg();
        protected int iDebugCounter = 0;
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        helper.DebugMsg helper.IDebugable.getDebugMsg(bool showNextInstance)
        {
            createDebugMsg(showNextInstance);

            return oMsg;
        }
        protected void createDebugMsg(bool showNextInstance)
        {
            if (showNextInstance)
                iDebugCounter++;

            if (iDebugCounter >= oVars.iInstanceCount)
                iDebugCounter = 0;

            oMsg.message = "Positiong of [" + iDebugCounter + "] = " + oVars.oPosition[iDebugCounter] + "\ncount=" + oVars.iInstanceCount +
                "\nWorld=" + oVars.oWorld[iDebugCounter];

            oMsg.position = oVars.oPosition[iDebugCounter];
            oMsg.scale = 30;
        }
        protected void registerDebug()
        {
            oVars.oConsole.registerDebug(this);
        }
        #endregion
    }
}